package tk.xboot.boot.util;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;

public class GenericReflection {

    private static final Hashtable<Class<?>, Hashtable<Integer, Constructor<?>>> cacheConstructor = new Hashtable<Class<?>, Hashtable<Integer,Constructor<?>>>();
    private static final Hashtable<Class<?>, Hashtable<Integer, Constructor<?>>> cacheDeclaredConstructor = new Hashtable<Class<?>, Hashtable<Integer,Constructor<?>>>();
    private static final Hashtable<Class<?>, Method[]> cacheMethods = new Hashtable<Class<?>, Method[]>();
    private static final Hashtable<Class<?>, Field[]> cacheFields = new Hashtable<Class<?>, Field[]>();
    private static final Hashtable<Class<?>, Hashtable<String, Field[]>> cacheConditionFields = new Hashtable<Class<?>, Hashtable<String, Field[]>>();
    private static final Hashtable<Class<?>, Hashtable<String, Field>> cacheField = new Hashtable<Class<?>, Hashtable<String, Field>>();
    private static final Hashtable<Class<?>, Hashtable<String, Method>> cacheDeclaredMethods = new Hashtable<Class<?>, Hashtable<String,Method>>();
    private static final Hashtable<Class<?>, Hashtable<String, Hashtable<Integer, Method>>> cacheMethod = new Hashtable<Class<?>, Hashtable<String,Hashtable<Integer,Method>>>();
    private static final Hashtable<Class<?>, HashSet<Class<?>>> interfaces = new Hashtable<Class<?>, HashSet<Class<?>>>();

    private GenericReflection() {}

    public static Object getValue(Class<?> Class, String nameField, Object reference) {
        try {
            return getDeclaredField(Class, nameField).get(reference);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void setValue(Class<?> Class, String nameField, Object value, Object reference) {
        try {
            getDeclaredField(Class, nameField).set(reference, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Field getDeclaredField(Class<?> Class, String fieldName) throws SecurityException, NoSuchFieldException {
        Hashtable<String, Field> fields = cacheField.get(Class);

        if(fields == null)
            cacheField.put(Class, fields = new Hashtable<String, Field>());

        Field field = fields.get(fieldName);

        if(field == null) {
            (field = Class.getDeclaredField(fieldName)).setAccessible(true);
            fields.put(fieldName, field);
        }

        return field;
    }

    public static Field[] getDeclaredFields(Class<?> Class) {
        Field[] fields = cacheFields.get(Class);

        if(fields == null) {
            fields = Class.getDeclaredFields();
            for (Field f : fields)
                f.setAccessible(true);

            cacheFields.put(Class, fields);
        }

        return fields;
    }

    public static void registerDeclaredFields(Class<?> Class, String id, Field[] fields) {
        Hashtable<String, Field[]> hashFields = cacheConditionFields.get(Class);
        if(hashFields == null)
            cacheConditionFields.put(Class, hashFields = new Hashtable<String, Field[]>());

        hashFields.put(id, fields);
    }

    public static Field[] getDeclaredFieldsByConditionId(Class<?> Class, String id) {
        Hashtable<String, Field[]> hashFields = cacheConditionFields.get(Class);
        if(hashFields != null)
            return hashFields.get(id);

        return null;
    }

    public static Field[] getDeclaredFieldsByCondition(Class<?> Class, String id, Condition<Field> condition) {
        return getDeclaredFieldsByCondition(Class, id, condition, false);
    }

    public static Field[] getDeclaredFieldsByCondition(Class<?> Class, String id, Condition<Field> condition, boolean considerParents)
    {
        final ArrayList<Field> _fields = new ArrayList<Field>();

        Field[] fields = getDeclaredFields(Class);

        for (Field field : fields) if(condition.init(field))
            _fields.add(field);

        if(considerParents) {
            final Class<?>[] parents = ClassUtils.getParents(Class);
            for (Class<?> parent : parents) {
                fields = getDeclaredFields(parent);
                for (Field field : fields) {
                    if(condition.init(field))
                        _fields.add(field);
                }
            }
        }

        fields = _fields.toArray(new Field[_fields.size()]);
        registerDeclaredFields(Class, id, fields);

        return fields;
    }

    public static Method getDeclaredMethod(Class<?> Class, String methodName, Class<?>... parameterTypes) throws SecurityException, NoSuchMethodException
    {
        Hashtable<String, Method> methods = cacheDeclaredMethods.get(Class);

        if(methods == null)
            cacheDeclaredMethods.put(Class, methods = new Hashtable<String, Method>());

        Method method = methods.get(methodName);

        if(method == null) {
            (method = Class.getDeclaredMethod(methodName, parameterTypes)).setAccessible(true);
            methods.put(methodName, method);
        }

        return method;
    }

    public static Method getMethod(Class<?> Class, String methodName, Class<?>... parameterTypes) throws SecurityException, NoSuchMethodException
    {
        Hashtable<String, Hashtable<Integer, Method>> methods = cacheMethod.get(Class);

        if(methods == null)
            cacheMethod.put(Class, methods = new Hashtable<String, Hashtable<Integer,Method>>());

        Hashtable<Integer, Method> methodArgs = methods.get(methodName);

        if(methodArgs == null)
            methods.put(methodName, methodArgs = new Hashtable<Integer, Method>());

        final int hashCode = Arrays.hashCode(parameterTypes);

        Method method = methodArgs.get(hashCode);

        if(method == null) {
            (method = Class.getMethod(methodName, parameterTypes)).setAccessible(true);
            methodArgs.put(hashCode, method);
        }

        return method;
    }

    public static Method[] getDeclaredMethods(Class<?> Class)
    {
        Method[] methods = cacheMethods.get(Class);

        if(methods == null)
            cacheMethods.put(Class, methods = Class.getDeclaredMethods());

        return methods;
    }

    public static void setFinalAccessible(Field field) throws SecurityException,
            NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    }

    @SuppressWarnings("unchecked")
    public static<C> Constructor<C> getConstrutor(Class<C> Class, Class<?>... parameterTypes)
            throws SecurityException, NoSuchMethodException {
        final int hashcode = Arrays.hashCode(parameterTypes);

        Hashtable<Integer, Constructor<?>> constructors = cacheConstructor.get(Class);

        if(constructors == null)
            cacheConstructor.put(Class, constructors = new Hashtable<Integer, Constructor<?>>());

        Constructor<C> constructor = (Constructor<C>) constructors.get(hashcode);

        if(constructor == null) {
            (constructor = Class.getConstructor(parameterTypes)).setAccessible(true);
            constructors.put(hashcode, constructor);
        }

        return constructor;
    }

    @SuppressWarnings("unchecked")
    public static<C> Constructor<C> getDeclaredConstrutor(Class<C> Class, Class<?>... parameterTypes)
            throws SecurityException, NoSuchMethodException {
        final int hashcode = Arrays.hashCode(parameterTypes);

        Hashtable<Integer, Constructor<?>> constructors = cacheDeclaredConstructor.get(Class);

        if(constructors == null)
            cacheDeclaredConstructor.put(Class, constructors = new Hashtable<Integer, Constructor<?>>());

        Constructor<C> constructor = (Constructor<C>) constructors.get(hashcode);

        if(constructor == null) {
            (constructor = Class.getDeclaredConstructor(parameterTypes)).setAccessible(true);
            constructors.put(hashcode, constructor);
        }

        return constructor;
    }

    public static boolean hasInterface(Class<?> Class, Class<?> Interface)
    {
        HashSet<Class<?>> interfs = interfaces.get(Class);
        if(interfs == null)
            interfaces.put(Class, interfs = new HashSet<Class<?>>(Arrays.asList(Class.getInterfaces())));

        return interfs.contains(Interface);
    }

    public static Enum<?> getEnumValue(Class<?> clazz, String name) {
        if(name == null) {
            return null;
        }

        Object[] enums = clazz.getEnumConstants();
        for (int i = -1, s = enums.length; ++i < s;) {
            Enum<?> _enum = (Enum<?>) enums[i];
            if(_enum.name().equals(name)) {
                return _enum;
            }
        }

        return null;
    }

    public static class NoThrow {

        public static Object getValue(Field field, Object reference) {
            try {
                return field.get(reference);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static void setFinalStaticValue(Class<?> c, String fieldName, Object value) {
            setFinalValue(c, fieldName, value, null);
        }

        public static void setFinalValue(Class<?> c, String fieldName, Object value, Object reference) {
            GenericReflection.NoThrow.setFinalValue(GenericReflection.NoThrow.getDeclaredField(c, fieldName), value, reference);
        }

        public static void setFinalValue(Field field, Object value, Object reference) {
            try {
                setFinalAccessible(field);
                field.set(reference, value);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static void setValue(Field field, Object value, Object reference) {
            try {
                field.set(reference, value);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static Field getDeclaredField(Class<?> Class, String fieldName) {
            try {
                return GenericReflection.getDeclaredField(Class, fieldName);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static<C> Constructor<C> getConstrutor(Class<C> Class, Class<?>... parameterTypes) {
            try {
                return GenericReflection.getConstrutor(Class, parameterTypes);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static<C> Constructor<C> getDeclaredConstrutor(Class<C> Class, Class<?>... parameterTypes) {
            try {
                return GenericReflection.getDeclaredConstrutor(Class, parameterTypes);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static Method getMethod(Class<?> Class, String methodName, Class<?>... parameterTypes) {
            try {
                return GenericReflection.getMethod(Class, methodName, parameterTypes);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static Object invokeMethod(Class<?> Class, String methodName, Object... objs) {
            try {
                Class<?>[] parameters = new Class<?>[objs.length];
                for (int i = -1, s = parameters.length; ++i < s;) {
                    parameters[i] = objs[i].getClass();
                }

                return GenericReflection.getMethod(Class, methodName, parameters).invoke(objs);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static Method getDeclaredMethod(Class<?> Class, String methodName, Class<?>... parameterTypes) {
            try {
                return GenericReflection.getDeclaredMethod(Class, methodName, parameterTypes);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static Object invakeDeclaredMethod(Class<?> Class, String methodName, Object... objs) {
            try {
                Class<?>[] parameters = new Class<?>[objs.length];
                for (int i = -1, s = parameters.length; ++i < s;) {
                    parameters[i] = objs[i].getClass();
                }

                return GenericReflection.getDeclaredMethod(Class, methodName, parameters).invoke(objs);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static Object newInstance(String className, Class<?>[] parameterTypes, Object... objects) {
            try {
                return newInstance(Class.forName(className), parameterTypes, objects);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        public static<C> C newInstance(Class<C> _class, Class<?>[] parameterTypes, Object... objects) {
            try {
                return getDeclaredConstrutor(_class, parameterTypes).newInstance(objects);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static void setFinalAccessible(Field field) {
            try {
                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public interface Condition<R extends AccessibleObject> {
        public abstract boolean init(R arg0);
    }
}
